/******************************************************************************* * Copyright 2011 See AUTHORS file. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. ******************************************************************************/ package com.badlogic.gdx.graphics; import java.nio.IntBuffer; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import com.badlogic.gdx.Application; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.assets.AssetLoaderParameters.LoadedCallback; import com.badlogic.gdx.assets.AssetManager; import com.badlogic.gdx.assets.loaders.AssetLoader; import com.badlogic.gdx.assets.loaders.TextureLoader.TextureParameter; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.graphics.Pixmap.Blending; import com.badlogic.gdx.graphics.Pixmap.Format; import com.badlogic.gdx.graphics.TextureData.TextureDataType; import com.badlogic.gdx.graphics.glutils.ETC1TextureData; import com.badlogic.gdx.graphics.glutils.FileTextureData; import com.badlogic.gdx.graphics.glutils.MipMapGenerator; import com.badlogic.gdx.graphics.glutils.PixmapTextureData; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.utils.BufferUtils; import com.badlogic.gdx.utils.Disposable; import com.badlogic.gdx.utils.GdxRuntimeException; /** * <p> * A Texture wraps a standard OpenGL ES texture. * </p> * * <p> * A Texture can be managed. If the OpenGL context is lost all managed textures get invalidated. This happens when a * user switches to another application or receives an incoming call. Managed textures get reloaded automatically. * </p> * * <p> * A Texture has to be bound via the {@link Texture#bind()} method in order for it to be applied to geometry. The * texture will be bound to the currently active texture unit specified via {@link GLCommon#glActiveTexture(int)}. * </p> * * <p> * You can draw {@link Pixmap}s to a texture at any time. The changes will be automatically uploaded to texture memory. * This is of course not extremely fast so use it with care. It also only works with unmanaged textures. * </p> * * <p> * A Texture must be disposed when it is no longer used * </p> * * @author badlogicgames@gmail.com */ public class Texture implements Disposable { static private boolean enforcePotImages = true; private static AssetManager assetManager; final static Map<Application, List<Texture>> managedTextures = new HashMap<Application, List<Texture>>(); public enum TextureFilter { Nearest(GL10.GL_NEAREST), Linear(GL10.GL_LINEAR), MipMap(GL10.GL_LINEAR_MIPMAP_LINEAR), MipMapNearestNearest( GL10.GL_NEAREST_MIPMAP_NEAREST), MipMapLinearNearest(GL10.GL_LINEAR_MIPMAP_NEAREST), MipMapNearestLinear( GL10.GL_NEAREST_MIPMAP_LINEAR), MipMapLinearLinear(GL10.GL_LINEAR_MIPMAP_LINEAR); final int glEnum; TextureFilter(int glEnum) { this.glEnum = glEnum; } public boolean isMipMap() { return glEnum != GL10.GL_NEAREST && glEnum != GL10.GL_LINEAR; } public int getGLEnum() { return glEnum; } } public enum TextureWrap { MirroredRepeat(GL20.GL_MIRRORED_REPEAT), ClampToEdge(GL10.GL_CLAMP_TO_EDGE), Repeat(GL10.GL_REPEAT); final int glEnum; TextureWrap(int glEnum) { this.glEnum = glEnum; } public int getGLEnum() { return glEnum; } } private static final IntBuffer buffer = BufferUtils.newIntBuffer(1); TextureFilter minFilter = TextureFilter.Linear; TextureFilter magFilter = TextureFilter.Linear; TextureWrap uWrap = TextureWrap.ClampToEdge; TextureWrap vWrap = TextureWrap.ClampToEdge; int glHandle; TextureData data; public Texture(String internalPath) { this(Gdx.files.internal(internalPath)); } public Texture(FileHandle file) { this(file, null, false); } public Texture(FileHandle file, boolean useMipMaps) { this(file, null, useMipMaps); } public Texture(FileHandle file, Format format, boolean useMipMaps) { if (file.name().endsWith(".etc1")) { create(new ETC1TextureData(file, useMipMaps)); } else { create(new FileTextureData(file, null, format, useMipMaps)); } } public Texture(Pixmap pixmap) { this(new PixmapTextureData(pixmap, null, false, false)); } public Texture(Pixmap pixmap, boolean useMipMaps) { this(new PixmapTextureData(pixmap, null, useMipMaps, false)); } public Texture(Pixmap pixmap, Format format, boolean useMipMaps) { this(new PixmapTextureData(pixmap, format, useMipMaps, false)); } public Texture(int width, int height, Format format) { this(new PixmapTextureData(new Pixmap(width, height, format), null, false, true)); } public Texture(TextureData data) { create(data); } private void create(TextureData data) { glHandle = createGLHandle(); load(data); if (data.isManaged()) addManagedTexture(Gdx.app, this); } public static int createGLHandle() { buffer.position(0); buffer.limit(buffer.capacity()); Gdx.gl.glGenTextures(1, buffer); return buffer.get(0); } public String getFileName() { if (data == null) return null; return data.getFileName(); } public void reloadNew(AssetManager manager) { String fileName = getFileName(); if (fileName == null) { System.out.println("====name ==null "); return; } TextureParameter params = new TextureParameter(); params.textureData = this.getTextureData(); params.minFilter = this.getMinFilter(); params.magFilter = this.getMagFilter(); params.wrapU = this.getUWrap(); params.wrapV = this.getVWrap(); params.genMipMaps = this.data.useMipMaps(); // not sure about this? params.texture = this; // special parameter which will ensure that the references stay the same. // unload the texture, create a new gl handle then reload it. this.glHandle = Texture.createGLHandle(); manager.load(fileName, Texture.class, params); } public void load(TextureData data) { if (this.data != null && data.isManaged() != this.data.isManaged()) throw new GdxRuntimeException("New data must have the same managed status as the old data"); this.data = data; if (!data.isPrepared()) data.prepare(); if (data.getType() == TextureDataType.Pixmap) { Pixmap pixmap = data.consumePixmap(); uploadImageData(pixmap); if (data.disposePixmap()) pixmap.dispose(); setFilter(minFilter, magFilter); setWrap(uWrap, vWrap); } if (data.getType() == TextureDataType.Compressed) { Gdx.gl.glBindTexture(GL10.GL_TEXTURE_2D, glHandle); data.consumeCompressedData(); setFilter(minFilter, magFilter); setWrap(uWrap, vWrap); } if (data.getType() == TextureDataType.Float) { Gdx.gl.glBindTexture(GL10.GL_TEXTURE_2D, glHandle); data.consumeCompressedData(); setFilter(minFilter, magFilter); setWrap(uWrap, vWrap); } Gdx.gl.glBindTexture(GL10.GL_TEXTURE_2D, 0); } private void uploadImageData(Pixmap pixmap) { if (enforcePotImages && Gdx.gl20 == null && (!MathUtils.isPowerOfTwo(data.getWidth()) || !MathUtils.isPowerOfTwo(data.getHeight()))) { throw new GdxRuntimeException("Texture width and height must be powers of two: " + data.getWidth() + "x" + data.getHeight()); } boolean disposePixmap = false; if (data.getFormat() != pixmap.getFormat()) { Pixmap tmp = new Pixmap(pixmap.getWidth(), pixmap.getHeight(), data.getFormat()); Blending blend = Pixmap.getBlending(); Pixmap.setBlending(Blending.None); tmp.drawPixmap(pixmap, 0, 0, 0, 0, pixmap.getWidth(), pixmap.getHeight()); Pixmap.setBlending(blend); pixmap = tmp; disposePixmap = true; } Gdx.gl.glBindTexture(GL10.GL_TEXTURE_2D, glHandle); Gdx.gl.glPixelStorei(GL10.GL_UNPACK_ALIGNMENT, 1); if (data.useMipMaps()) { MipMapGenerator.generateMipMap(pixmap, pixmap.getWidth(), pixmap.getHeight(), disposePixmap); } else { Gdx.gl.glTexImage2D(GL10.GL_TEXTURE_2D, 0, pixmap.getGLInternalFormat(), pixmap.getWidth(), pixmap.getHeight(), 0, pixmap.getGLFormat(), pixmap.getGLType(), pixmap.getPixels()); if (disposePixmap) { pixmap.dispose(); } } Gdx.app.log("Texture", "load texture: " + data.getFileName()); } /** * Used internally to reload after context loss. Creates a new GL handle then calls {@link #load(TextureData)}. Use * this only if you know what you do! */ private void reload() { if (!data.isManaged()) throw new GdxRuntimeException("Tried to reload unmanaged Texture"); glHandle = createGLHandle(); load(data); } /** * Binds this texture. The texture will be bound to the currently active texture unit specified via * {@link GLCommon#glActiveTexture(int)}. */ public void bind() { Gdx.gl.glBindTexture(GL10.GL_TEXTURE_2D, glHandle); } /** * Binds the texture to the given texture unit. Sets the currently active texture unit via * {@link GLCommon#glActiveTexture(int)}. * * @param unit * the unit (0 to MAX_TEXTURE_UNITS). */ public void bind(int unit) { Gdx.gl.glActiveTexture(GL10.GL_TEXTURE0 + unit); Gdx.gl.glBindTexture(GL10.GL_TEXTURE_2D, glHandle); } /** * Draws the given {@link Pixmap} to the texture at position x, y. No clipping is performed so you have to make sure * that you draw only inside the texture region. Note that this will only draw to mipmap level 0! * * @param pixmap * The Pixmap * @param x * The x coordinate in pixels * @param y * The y coordinate in pixels */ public void draw(Pixmap pixmap, int x, int y) { if (data.isManaged()) throw new GdxRuntimeException("can't draw to a managed texture"); Gdx.gl.glBindTexture(GL10.GL_TEXTURE_2D, glHandle); Gdx.gl.glTexSubImage2D(GL10.GL_TEXTURE_2D, 0, x, y, pixmap.getWidth(), pixmap.getHeight(), pixmap.getGLFormat(), pixmap.getGLType(), pixmap.getPixels()); } /** @return the width of the texture in pixels */ public int getWidth() { return data.getWidth(); } /** @return the height of the texture in pixels */ public int getHeight() { return data.getHeight(); } public TextureFilter getMinFilter() { return minFilter; } public TextureFilter getMagFilter() { return magFilter; } public TextureWrap getUWrap() { return uWrap; } public TextureWrap getVWrap() { return vWrap; } public TextureData getTextureData() { return data; } /** @return whether this texture is managed or not. */ public boolean isManaged() { return data.isManaged(); } public int getTextureObjectHandle() { return glHandle; } /** * Sets the {@link TextureWrap} for this texture on the u and v axis. This will bind this texture! * * @param u * the u wrap * @param v * the v wrap */ public void setWrap(TextureWrap u, TextureWrap v) { this.uWrap = u; this.vWrap = v; bind(); Gdx.gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, u.getGLEnum()); Gdx.gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, v.getGLEnum()); } public void setFilter(TextureFilter minFilter, TextureFilter magFilter) { this.minFilter = minFilter; this.magFilter = magFilter; bind(); Gdx.gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, minFilter.getGLEnum()); Gdx.gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, magFilter.getGLEnum()); } /** Disposes all resources associated with the texture */ public void dispose() { // this is a hack. reason: we have to set the glHandle to 0 for textures that are // reloaded through the asset manager as we first remove (and thus dispose) the texture // and then reload it. the glHandle is set to 0 in invalidateAllTextures prior to // removal from the asset manager. if (glHandle == 0) return; buffer.put(0, glHandle); Gdx.gl.glDeleteTextures(1, buffer); if (data.isManaged()) { if (managedTextures.get(Gdx.app) != null) managedTextures.get(Gdx.app).remove(this); } glHandle = 0; } /** * @param enforcePotImages * whether to enforce power of two images in OpenGL ES 1.0 or not. */ static public void setEnforcePotImages(boolean enforcePotImages) { Texture.enforcePotImages = enforcePotImages; } private static void addManagedTexture(Application app, Texture texture) { List<Texture> managedTexureList = managedTextures.get(app); if (managedTexureList == null) managedTexureList = new ArrayList<Texture>(); managedTexureList.add(texture); managedTextures.put(app, managedTexureList); } /** Clears all managed textures. This is an internal method. Do not use it! */ public static void clearAllTextures(Application app) { managedTextures.remove(app); } /** Invalidate all managed textures. This is an internal method. Do not use it! */ public static void invalidateAllTextures(Application app) { List<Texture> managedTexureList = managedTextures.get(app); if (managedTexureList == null) return; if (assetManager == null) { for (int i = 0; i < managedTexureList.size(); i++) { Texture texture = managedTexureList.get(i); texture.reload(); } } else { // first we have to make sure the AssetManager isn't loading anything anymore, // otherwise the ref counting trick below wouldn't work (when a texture is // currently on the task stack of the manager.) assetManager.finishLoading(); // next we go through each texture and reload either directly or via the // asset manager. List<Texture> textures = new ArrayList<Texture>(managedTexureList); for (Texture texture : textures) { String fileName = assetManager.getAssetFileName(texture); if (fileName == null) { texture.reload(); } else { // get the ref count of the texture, then set it to 0 so we // can actually remove it from the assetmanager. Also set the // handle to zero, otherwise we might accidentially dispose // already reloaded textures. final int refCount = assetManager.getReferenceCount(fileName); assetManager.setReferenceCount(fileName, 0); texture.glHandle = 0; // create the parameters, passing the reference to the texture as // well as a callback that sets the ref count. TextureParameter params = new TextureParameter(); params.textureData = texture.getTextureData(); params.minFilter = texture.getMinFilter(); params.magFilter = texture.getMagFilter(); params.wrapU = texture.getUWrap(); params.wrapV = texture.getVWrap(); params.genMipMaps = texture.data.useMipMaps(); // not sure about this? params.texture = texture; // special parameter which will ensure that the references stay the same. params.loadedCallback = new LoadedCallback() { @Override public void finishedLoading(AssetManager assetManager, String fileName, Class type) { assetManager.setReferenceCount(fileName, refCount); } }; // unload the texture, create a new gl handle then reload it. assetManager.unload(fileName); texture.glHandle = Texture.createGLHandle(); assetManager.load(fileName, Texture.class, params); } } managedTexureList.clear(); managedTexureList.addAll(textures); } } /** * Sets the {@link AssetManager}. When the context is lost, textures managed by the asset manager are reloaded by * the manager on a separate thread (provided that a suitable {@link AssetLoader} is registered with the manager). * Textures not managed by the AssetManager are reloaded via the usual means on the rendering thread. * * @param manager * the asset manager. */ public static void setAssetManager(AssetManager manager) { Texture.assetManager = manager; } public static String getManagedStatus() { StringBuilder builder = new StringBuilder(); builder.append("Managed textures/app: { "); for (Application app : managedTextures.keySet()) { builder.append(managedTextures.get(app).size()); builder.append(" "); } builder.append("}"); return builder.toString(); } /** @return the number of managed textures currently loaded */ public static int getNumManagedTextures() { return managedTextures.get(Gdx.app).size(); } }